/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "vsf.h"
#include <stdio.h>
#include <stdlib.h>

#define VSFIP_HTTPC_AGENT				"vsfip_httpc"
#define VSFIP_HTTPC_SOCKET_TIMEOUT		4000

#ifdef VSFIP_HTTPC_DEBUG
#define vsfip_httpc_printf				vsfdbg_printf
#else
#define vsfip_httpc_printf(fmt, ...)	vsfdbg_printf("httpc: " fmt, ##__VA_ARGS__)
#endif

#define VSFSM_EVT_HTTPC_STREAM			(VSFSM_EVT_USER_LOCAL + 0x01)

static vsf_err_t vsfip_httpc_buildreq(struct vsfip_buffer_t *buf,
		const char *verb, const char *host, const char *path,
		const char *content_type, uint32_t content_length)
{
	char *dst = (char *)buf->app.buffer;

	strcpy(dst, verb);
	strcat(dst, " ");
	strcat(dst, path);
	strcat(dst, " HTTP/1.1\r\n" "Host: ");
	strcat(dst, host);
	if (content_type != NULL)
	{
		strcat(dst, "\r\n" "Content-Type: ");
		strcat(dst, content_type);

		if (content_length)
		{
			char *end;

			strcat(dst, "\r\n" "Content-Length: ");
			end = &dst[strlen(dst)];
			sprintf(end, "%d", content_length);
		}
	}
	strcat(dst, "\r\n" "Connection: keep-alive\r\n"\
				"User-Agent: " VSFIP_HTTPC_AGENT "\r\n\r\n");
	buf->app.size = strlen((const char *)dst);
	return VSFERR_NONE;
}

static vsf_err_t vsfip_httpc_prasehead(struct vsfip_httpc_t *httpc,
									struct vsfip_buffer_t *buf)
{
#define HTTPC_RESP_MATCH(ptr, str)		\
			!strncmp((const char *)(cur), (str), sizeof(str) - 1)

	uint8_t *cur = buf->app.buffer;
	uint8_t *end = buf->app.buffer + buf->app.size;

	if (!HTTPC_RESP_MATCH(cur, "HTTP/1.1 "))
		return VSFERR_FAIL;
	cur += sizeof("HTTP/1.1 ") - 1;

	httpc->resp_code = atoi((const char *)cur);
	if (httpc->resp_code != 200)
		return VSFERR_FAIL;

	cur = (uint8_t *)strchr((const char *)cur, '\n') + 1;
	while (cur < end)
	{
		if (HTTPC_RESP_MATCH(cur, "Content-"))
		{
			cur += sizeof("Content-") - 1;

			if (HTTPC_RESP_MATCH(cur, "Type: "))
			{
				cur += sizeof("Type: ") - 1;
				httpc->resp_type = cur;
			}
			else if (HTTPC_RESP_MATCH(cur, "Length: "))
			{
				cur += sizeof("Length: ") - 1;
				httpc->resp_length = atoi((char const *)cur);
			}
		}
		else if (cur[0] == '\r' && cur[1] == '\n')
		{
			cur += 2;
			buf->app.size -= cur - buf->app.buffer;
			buf->app.buffer = cur;
			return VSFERR_NONE;
		}

		cur = (uint8_t *)strchr((const char *)cur, '\n') + 1;
	}

	return VSFERR_NOT_READY;
}

static void vsfip_httpc_stream_inout(void *p)
{
	vsfsm_post_evt_pending((struct vsfsm_t *)p, VSFSM_EVT_HTTPC_STREAM);
}

vsf_err_t vsfip_httpc_run_stream(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		const char *verb, const char *path, const char *content_type,
		uint32_t content_length, struct vsf_stream_t *tx_stream,
		struct vsf_stream_t *rx_stream)
{
	struct vsfip_httpc_t *httpc = (struct vsfip_httpc_t *)pt->user_data;
	vsf_err_t err = VSFERR_NONE;

	vsfsm_pt_begin(pt);

	httpc->local_pt.sm = pt->sm;
	if (httpc->buf == NULL)
		httpc->buf = VSFIP_TCPBUF_GET(VSFIP_CFG_TCP_MSS);
	if (httpc->buf == NULL)
	{
		vsfip_httpc_printf("fail to get sock buffer" VSFCFG_DEBUG_LINEEND);
		return VSFERR_NOT_ENOUGH_RESOURCES;
	}

	err = vsfip_httpc_buildreq(httpc->buf, verb, httpc->host, path, content_type,
				content_length);
	if (err != 0)
	{
		vsfip_httpc_printf("fail to build request" VSFCFG_DEBUG_LINEEND);
		return err;
	}

#ifdef VSFIP_HTTPC_DEBUG
	vsfip_httpc_printf("send http head:" VSFCFG_DEBUG_LINEEND);
	httpc->buf->app.buffer[httpc->buf->app.size] = '\0';
	vsfdbg_prints((char *)httpc->buf->app.buffer);
	vsfdbg_prints(VSFCFG_DEBUG_LINEEND);
#endif

	httpc->local_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = vsfip_tcp_send(&httpc->local_pt, evt, httpc->so, httpc->buf, false);
	if (err > 0) return err; else if (err < 0)
	{
		vsfip_httpc_printf("fail to send request" VSFCFG_DEBUG_LINEEND);
		return err;
	}
	httpc->buf = NULL;
	httpc->so->tx_timeout_ms = 0;

	if (tx_stream != NULL)
	{
		// send stream
		tx_stream->callback_rx.param = pt->sm;
		tx_stream->callback_rx.on_inout = vsfip_httpc_stream_inout;
		vsfstream_connect_rx(tx_stream);

		httpc->data_length = content_length;
		while (httpc->data_length)
		{
			httpc->buf = VSFIP_TCPBUF_GET(VSFIP_CFG_TCP_MSS);
			if (httpc->buf == NULL)
			{
				vsfip_httpc_printf("fail to get sock buffer" VSFCFG_DEBUG_LINEEND);
				return VSFERR_NOT_ENOUGH_RESOURCES;
			}

			while (!vsfstream_get_data_size(tx_stream))
				vsfsm_pt_wfe(pt, VSFSM_EVT_HTTPC_STREAM);

			httpc->buf->app.size = vsfstream_read(tx_stream, &httpc->buf->app);
			httpc->data_length -= httpc->buf->app.size;

#ifdef VSFIP_HTTPC_DEBUG
			vsfip_httpc_printf("send http data:" VSFCFG_DEBUG_LINEEND);
			httpc->buf->app.buffer[httpc->buf->app.size] = '\0';
			vsfdbg_prints((char *)httpc->buf->app.buffer);
			vsfdbg_prints(VSFCFG_DEBUG_LINEEND);
#endif

			httpc->local_pt.state = 0;
			vsfsm_pt_entry(pt);
			err = vsfip_tcp_send(&httpc->local_pt, evt, httpc->so, httpc->buf, true);
			if (err > 0) return err; else if (err < 0)
			{
				vsfip_httpc_printf("fail to send data" VSFCFG_DEBUG_LINEEND);
				return err;
			}
			httpc->buf = NULL;
			httpc->so->tx_timeout_ms = 0;
		}
	}

	// get response
	httpc->resp_length = 0;
	httpc->resp_curptr = 0;
	while (1)
	{
		httpc->local_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = vsfip_tcp_recv(&httpc->local_pt, evt, httpc->so, &httpc->buf);
		if (err > 0) return err; else if (err < 0)
		{
			vsfip_httpc_printf("fail to receive response" VSFCFG_DEBUG_LINEEND);
			return err;
		}

#ifdef VSFIP_HTTPC_DEBUG
		vsfip_httpc_printf("receviced:" VSFCFG_DEBUG_LINEEND);
		httpc->buf->app.buffer[httpc->buf->app.size] = '\0';
		vsfdbg_prints((char *)httpc->buf->app.buffer);
		vsfdbg_prints(VSFCFG_DEBUG_LINEEND);
#endif

		if (0 == httpc->resp_length)
		{
			err = vsfip_httpc_prasehead(httpc, httpc->buf);
			if (err > 0)
			{
				vsfip_buffer_release(httpc->buf);
				continue;
			}
			else if (err < 0)
			{
				vsfip_httpc_printf("fail to parse head" VSFCFG_DEBUG_LINEEND);
				return err;
			}

			if (0 == httpc->resp_length)
				return err;

			if (rx_stream != NULL)
			{
				rx_stream->callback_tx.param = pt->sm;
				rx_stream->callback_tx.on_inout = vsfip_httpc_stream_inout;
				vsfstream_connect_tx(rx_stream);
			}
		}

		if (httpc->resp_length > 0)
		{
			uint32_t written_size;

			while (httpc->buf->app.size > 0)
			{
				if (rx_stream != NULL)
				{
					while (!vsfstream_get_free_size(rx_stream))
						vsfsm_pt_wfe(pt, VSFSM_EVT_HTTPC_STREAM);
					written_size = vsfstream_write(rx_stream, &httpc->buf->app);
				}
				else
					written_size = httpc->buf->app.size;
				httpc->buf->app.size -= written_size;
				httpc->buf->app.buffer += written_size;
				httpc->resp_curptr += written_size;
			}
		}

		vsfip_buffer_release(httpc->buf);
		httpc->buf = NULL;

		if (httpc->resp_curptr >= httpc->resp_length)
			break;
	}

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

vsf_err_t vsfip_httpc_get_stream(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		const char *path, struct vsf_stream_t *rx_stream)
{
	return vsfip_httpc_run_stream(pt, evt, "GET", path, NULL, 0, NULL,
			rx_stream);
}

vsf_err_t vsfip_httpc_post_stream(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		const char *path, const char *content_type, uint32_t content_length,
		struct vsf_stream_t *tx_stream, struct vsf_stream_t *rx_stream)
{
	return vsfip_httpc_run_stream(pt, evt, "POST", path, content_type,
			content_length, tx_stream, rx_stream);
}

vsf_err_t vsfip_httpc_run_buf(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		const char *verb, const char *path, const char *content_type,
		uint32_t content_length, struct vsf_buffer_t *tx_buffer,
		struct vsf_buffer_t *rx_buffer)
{
	struct vsfip_httpc_buf_t *httpc = (struct vsfip_httpc_buf_t *)pt->user_data;

	if (!httpc->inited)
	{
		httpc->inited = true;
		if (tx_buffer != NULL)
		{
			httpc->tx_bufstream.mem.buffer = *tx_buffer;
			VSFSTREAM_INIT(&httpc->tx_bufstream);
			VSFSTREAM_CONNECT_TX(&httpc->tx_bufstream);
		}
		if (rx_buffer != NULL)
		{
			httpc->rx_bufstream.mem.buffer = *rx_buffer;
			VSFSTREAM_INIT(&httpc->rx_bufstream);
			VSFSTREAM_CONNECT_RX(&httpc->rx_bufstream);
		}
	}
	return vsfip_httpc_run_stream(pt, evt, verb, path,
			content_type, content_length,
			tx_buffer != NULL ? &httpc->tx_bufstream.stream : NULL,
			rx_buffer != NULL ? &httpc->rx_bufstream.stream : NULL);
}

vsf_err_t vsfip_httpc_get_buf(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		const char *path, struct vsf_buffer_t *rx_buffer)
{
	return vsfip_httpc_run_buf(pt, evt, "GET", path, NULL, 0, NULL, rx_buffer);
}

vsf_err_t vsfip_httpc_post_buf(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		const char *path, const char *content_type, uint32_t content_length,
		struct vsf_buffer_t *tx_buffer, struct vsf_buffer_t *rx_buffer)
{
	return vsfip_httpc_run_buf(pt, evt, "POST", path, content_type,
			content_length, tx_buffer, rx_buffer);
}

void vsfip_httpc_buf_init(struct vsfip_httpc_buf_t *httpc)
{
	httpc->inited = false;
	memset(&httpc->tx_bufstream, 0, sizeof(httpc->tx_bufstream));
	httpc->tx_bufstream.stream.op = &vsf_bufstream_op;
	httpc->tx_bufstream.mem.read = true;
	memset(&httpc->rx_bufstream, 0, sizeof(httpc->rx_bufstream));
	httpc->rx_bufstream.stream.op = &vsf_bufstream_op;
	httpc->rx_bufstream.mem.read = false;
}

vsf_err_t vsfip_httpc_connect(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
		const char *host)
{
	struct vsfip_httpc_t *httpc = (struct vsfip_httpc_t *)pt->user_data;
	vsf_err_t err = VSFERR_NONE;

	vsfsm_pt_begin(pt);

	httpc->local_pt.sm = pt->sm;
	err = vsfip_ip4_pton(&httpc->hostip.sin_addr, (char *)host);
	if (err)
	{
		httpc->local_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = vsfip_gethostbyname(&httpc->local_pt, evt, (char *)host,
				&httpc->hostip.sin_addr);
		if (err > 0) return err; else if (err < 0) return err;
	}
	vsfip_httpc_printf("host %d.%d.%d.%d" VSFCFG_DEBUG_LINEEND,
						httpc->hostip.sin_addr.addr.s_addr_buf[0],
						httpc->hostip.sin_addr.addr.s_addr_buf[1],
						httpc->hostip.sin_addr.addr.s_addr_buf[2],
						httpc->hostip.sin_addr.addr.s_addr_buf[3]);

	httpc->so = vsfip_socket(AF_INET, IPPROTO_TCP);
	if (httpc->so == NULL)
		return VSFERR_NOT_ENOUGH_RESOURCES;

	httpc->so->rx_timeout_ms = VSFIP_HTTPC_SOCKET_TIMEOUT;
	httpc->so->tx_timeout_ms = VSFIP_HTTPC_SOCKET_TIMEOUT;

	httpc->hostip.sin_port = httpc->port;
	httpc->local_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = vsfip_tcp_connect(&httpc->local_pt, evt, httpc->so, &httpc->hostip);
	if (err > 0) return err; else if (err < 0)
	{
		vsfip_httpc_printf("fail to connect to host" VSFCFG_DEBUG_LINEEND);
		vsfip_close(httpc->so);
		httpc->so = NULL;
	}
	httpc->host = host;

	vsfsm_pt_end(pt);
	return err;
}

vsf_err_t vsfip_httpc_disconnect(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfip_httpc_t *httpc = (struct vsfip_httpc_t *)pt->user_data;
	vsf_err_t err = VSFERR_NONE;

	vsfsm_pt_begin(pt);

	httpc->local_pt.sm = pt->sm;
	httpc->local_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = vsfip_tcp_close(&httpc->local_pt, evt, httpc->so);
	if (err > 0) return err; else if (err < 0)
		vsfip_httpc_printf("fail to disconnect from host" VSFCFG_DEBUG_LINEEND);

	vsfip_close(httpc->so);
	httpc->so = NULL;

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}
